package com.dream.service.basic;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.dream.common.utils.JsonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis封装模板及通用方法;
 */
@Service
public class RedisCacheService {
    @Autowired
    public RedisTemplate<String, String> redisTemplate;
    @Resource
    public HashOperations<String, String, String> hashOperations;
    @Resource
    public ListOperations<String, String> listOperations;
    @Resource
    public SetOperations<String, String> setOperations;
    @Resource
    public ZSetOperations<String, String> zSetOperations;
    @Resource
    public ValueOperations<String, String> valueOperations;


    /**
     * 获取自增id，正整数long 用于jpushsendNo.
     * 改用redis自身DB支持，不访问数据库
     *
     * @param name 业务id， 目前可以使用"jpush","chat_room"
     * @return long
     */
    public synchronized long getUniqNoSequency(String name) {
        return incr(name + "sequency");
    }


    /**
     * 是否在CD 冷却时间 的时间里, 控制调用间隔
     * 线程不安全的,注意并发,在外面加锁
     *
     * @param key   CD 的key
     * @param cdSec CD的时间
     * @return
     */
    public boolean isCD(String key, int cdSec) {
        boolean isCD = exists(key);
        if (!isCD) {
            setex(key, cdSec, "CD");
        }
        return isCD;
    }

    /**
     * 是否在CD 冷却时间 的时间里, 控制调用间隔
     * 加入线程安全,用 lockObj 参数的对象做锁对象
     *
     * @param key     CD 的key
     * @param cdSec   CD的时间
     * @param lockObj 线程锁对象
     */
    public boolean isCD(String key, int cdSec, Object lockObj) {
        synchronized (lockObj) {
            boolean isCD = exists(key);
            if (!isCD) {
                setex(key, cdSec, "CD");
            }
            return isCD;
        }
    }


    // ////////////// 常用方法的封装 ///////////////////////// //

    // ////////////// 公共 ///////////////////////////

    /**
     * 删除key, 如果key存在返回true, 否则返回false。
     */
    public void del(final String key) {
        redisTemplate.delete(key);
    }

    /**
     * 模糊前缀匹配删除
     *
     * @param prex
     */
    public void deleteByPrex(String prex) {
        Set<String> keys = redisTemplate.keys(prex + "*");
        redisTemplate.delete(keys);
    }

    /**
     * 模糊前缀匹配删除
     *
     * @param key
     */
    public List<String> getKeyListByKey(String key) {
        Set<String> keys = redisTemplate.keys(key + "*");
        return new ArrayList<>(keys);
    }

    // ////////////// 关于String ///////////////////////////

    /**
     * 如果key不存在, 返回null.
     */
    public String get(final String key) {
        return valueOperations.get(key);
    }


    /**
     * 获得 Object
     * 注意只能序列化 对象类型 , 不能序列化map 之类的 使用时注意之类的坑,做好単元测试
     */
    @SuppressWarnings("all")
    public <T> T getObject(String key, Class<T> cls) {
        String json = get(key);
        return json == null ? null : JsonUtils.parseObject(json, cls);
    }

    @SuppressWarnings("all")
    public LinkedHashMap<String, String> getMap(String key) {
        String json = get(key);
        if (StringUtils.isEmpty(json)) {
            return new LinkedHashMap<>();
        }
        LinkedHashMap<String, String> map = JSONObject.parseObject(json, new TypeReference<LinkedHashMap<String, String>>() {
        });
        return map;
    }

    /**
     * 获得 List<T>
     * 注意只能序列化 对象类型 , 不能序列化map 之类的 使用时注意之类的坑,做好単元测试
     */
    @SuppressWarnings("all")
    public <T> List<T> getObjectList(String key, Class<T> cls) {
        String json = get(key);
        return json == null ? null : JSON.parseArray(json, cls);
    }

    /**
     * 如果key不存在, 返回null.
     */
    public Long getAsLong(final String key) {
        String result = get(key);
        return result != null ? Long.valueOf(result) : null;
    }

    /**
     * 如果key不存在, 返回 null.
     */
    public Integer getAsInt(final String key) {
        String result = get(key);
        return result != null ? Integer.valueOf(result) : null;
    }

    public void set(final String key, final String value) {
        valueOperations.set(key, value);
    }

    /**
     * 设置 Object
     * 注意只能序列化 对象类型 , 不能序列化map 之类的 使用时注意之类的坑,做好単元测试
     */
    public void setexObject(final String key, final int seconds, Object object) {
        if (object != null) {
            setex(key, seconds, JsonUtils.toJSONString(object));
        }
    }

    /**
     * 设置 Object
     * 注意只能序列化 对象类型 , 不能序列化map 之类的 使用时注意之类的坑,做好単元测试
     */
    public void setObject(final String key, Object object) {
        set(key, JsonUtils.toJSONString(object));
    }

    public void setex(final String key, final int seconds, final String value) {
        valueOperations.set(key, value);
        redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 如果key还不存在则进行设置，返回true，否则返回false.
     */
    public boolean setnx(final String key, final String value) {
        return valueOperations.setIfAbsent(key, value);
    }

    /**
     * 将 key 中储存的数字值增一。
     * 如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 INCR 操作。
     *
     * @return 执行 INCR 命令之后 key 的值。
     */
    public Long incr(final String key) {
        return valueOperations.increment(key, 1);
    }

    /**
     * 查看键的剩余时间  返回三种值 -2(健已经不存在了)  -1(表示永久有效)  大于0的数(还有多少有效时间)
     */
    public long getExpire(final String key) {
        return redisTemplate.getExpire(key);
    }


    // ////////////// 关于List ///////////////////////////

    /**
     * 将一个或多个值 value 插入到列表 key 的表头
     * 如果有多个 value 值，那么各个 value 值按从左到右的顺序依次插入到表头： 比如说，对空列表 mylist 执行命令 LPUSH mylist a b c ，列表的值将是 c b a ，这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
     * 如果 key 不存在，一个空列表会被创建并执行 LPUSH 操作。
     *
     * @param key
     * @param value
     */
    public Long lpush(final String key, final String value) {
        return listOperations.leftPush(key, value);
    }


    /**
     * 将一个或多个值 value 插入到列表 key 的表尾
     */
    public Long rpush(final String key, final String value) {
        return listOperations.rightPush(key, value);

    }

    /**
     * 获得list下标值
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。
     * 你也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。
     */
    public String lindex(final String key, final long index) {
        return listOperations.index(key, index);
    }

    /**
     * 移除并返回列表 key 的头元素。
     */
    public String lpop(final String key) {
        return listOperations.leftPop(key);
    }


    /**
     * Return the length of the list stored at the specified key. If the key
     * does not exist zero is returned (the same behaviour as for empty lists).
     * If the value stored at key is not a list an error is returned.
     */
    public long llen(final String key) {
        return listOperations.size(key);
    }

    /**
     * 删除List中的第一个等于value的元素，value不存在或key不存在时返回0.
     */
    public long lremOne(final String key, final String value) {
        return listOperations.remove(key, 1, value);
    }

    /**
     * 返回指定区间元素,包括stop位置元素
     */
    public List<String> lrange(final String key, final long start, final long end) {
        return listOperations.range(key, start, end);
    }

    /**
     * 固定裁剪大小
     */
    public void fixTrimLpush(final String key, final String value, final long size) {
        if (listOperations.size(key) > size) {
            listOperations.trim(key, 0, size);
        }
        lpush(key, value);
    }


    // ////////////// 关于Sorted Set ///////////////////////////


    /**
     * 返回zSet长度, key不存在时返回0，key类型不是sorted set时抛出异常.
     */
    public long zcard(final String key) {
        return zSetOperations.size(key);
    }


    //***************hash 操作 ******************/

    public void hset(final String key, final String field, final String value) {
        hashOperations.put(key, field, value);
    }

    public String hget(final String key, final String field) {
        return hashOperations.get(key, field);
    }


    public Long hdel(final String key, final String field) {
        return hashOperations.delete(key, field);
    }


    public boolean hexists(final String key, final String field) {
        return hashOperations.hasKey(key, field);
    }


    public Long hlen(final String key) {
        return hashOperations.size(key);
    }


    /**
     * 设置一个超时的时间 (単位秒)
     */
    public boolean expire(final String key, final int seconds) {
        return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }


    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }


}
